﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Aphysoft.Vate.Graphics;
using Microsoft.Xna.Framework.Graphics;

namespace Aphysoft.Vate
{
    public class Transformation
    {
        #region Properties

        /// <summary>
        /// Calculated transformation matrix.
        /// </summary>
        protected Matrix matrix = Matrix.Identity;
        /// <summary>
        /// Gets transformation matrix.
        /// </summary>
        public Matrix Matrix
        {
            get 
            {
                if (_perf_init ||
                    _perf_position != position ||
                    _perf_rotationMatrix != rotationMatrix ||
                    _perf_scales != scales)
                {
                    matrix =
                        Matrix.Multiply( // then translate
                            Matrix.Multiply( // then rotate
                                Matrix.CreateScale(scales), // create scale
                                rotationMatrix),
                            Matrix.CreateTranslation(position)
                            );

                    // update performance variables
                    if (_perf_init) _perf_init = false;
                    _perf_position = position;
                    _perf_scales = scales;
                    _perf_rotationMatrix = rotationMatrix;
                }

                return matrix; 
            }
        }

        // Performance variables
        protected bool _perf_init = true;
        protected Vector3 _perf_position = Vector3.Zero;
        protected Vector3 _perf_scales = Vector3.One;
        protected Matrix _perf_rotationMatrix = Matrix.Identity;
        
        /// <summary>
        /// Gets forward vector of current matrix.
        /// </summary>
        public Vector3 Forward
        {
            get { return Matrix.Forward; }
        }
        /// <summary>
        /// Gets up vector of current matrix.
        /// </summary>
        public Vector3 Up
        {
            get { return Matrix.Up; }
        }
        /// <summary>
        /// Gets down vector of current matrix.
        /// </summary>
        public Vector3 Down
        {
            get { return Matrix.Down; }
        }
        /// <summary>
        /// Gets backward vector of current matrix.
        /// </summary>
        public Vector3 Backward
        {
            get { return Matrix.Backward; }
        }
        /// <summary>
        /// Gets left vector of current matrix.
        /// </summary>
        public Vector3 Left
        {
            get { return Matrix.Left; }
        }
        /// <summary>
        /// Gets right vector of current matrix.
        /// </summary>
        public Vector3 Right
        {
            get { return Matrix.Right; }
        }

        /// <summary>
        /// Current rotation matrix.
        /// </summary>
        protected Matrix rotationMatrix = Matrix.Identity;
        /// <summary>
        /// Gets or sets current rotation matrix.
        /// </summary>
        public Matrix RotationMatrix
        {
            get { return rotationMatrix; }
            set { rotationMatrix = value; }
        }
        /// <summary>
        /// Current scale matrix.
        /// </summary>
        protected Vector3 scales = Vector3.One;
        /// <summary>
        /// Gets or sets scale.
        /// </summary>
        public Vector3 Scales
        {
            get { return scales; }
            set { scales = value; }
        }
        /// <summary>
        /// Current position.
        /// </summary>
        protected Vector3 position = Vector3.Zero;
        /// <summary>
        /// Gets or sets position.
        /// </summary>
        public Vector3 Position
        {
            get { return matrix.Translation; }
            set 
            { 
                // update matrix translation.
                matrix.Translation = value; 
                // update position vector.
                position = value;
            }
        }

        /// <summary>
        /// Indicates this transformation has been added to transformation list.
        /// </summary>
        protected bool addedToList = false;
        /// <summary>
        /// Gets a value indicating whether the transformation was added to transformation list. Always return false
        /// in non-debug mode.
        /// </summary>
        public bool AddedToList
        {
            get { return addedToList; }
        }


        #endregion

        #region Constructor

        /// <summary>
        /// Initializes new instance of transformation.
        /// </summary>
        public Transformation()
        {
        }
        /// <summary>
        /// Initializes new instance of transformation in transformations list.
        /// </summary>
        /// <param name="addToList">true to add to transformations list.</param>
        public Transformation(bool addToList)
        {
            if (addToList)
            {
                addedToList = true;
                transformationsList.Add(this);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Rotates current transformation around an arbitrary angle.
        /// </summary>
        /// <param name="axis">The axis to rotate around. Vector relative to transformation position.</param>
        /// <param name="angle">The angle to rotate, in radian, around the axis vector.</param>
        public void Rotate(Vector3 axis, float angle)
        {
            rotationMatrix = Matrix.Multiply(rotationMatrix, Matrix.CreateFromAxisAngle(axis, angle));
        }
        /// <summary>
        /// Rotates current transforation around an arbitrary cosine angle.
        /// </summary>
        /// <param name="axis">The axis to rotate around. Vector relative to transformation position.</param>
        /// <param name="angle">The consine angle to rotate, -1 to 1, around the axis vector. The specified value will be
        /// clamped to -1 to 1 interval.</param>
        public void RotateByCosineAngle(Vector3 axis, float cosineAngle)
        {
            cosineAngle = MathHelper.Clamp(cosineAngle, -1, 1);
            Rotate(axis, (float)Math.Acos(cosineAngle));
        }
        /// <summary>
        /// Rotates around specific point and rotation direction at arbitrary angle.
        /// </summary>
        /// <param name="axisPoint">The axis point to rotate around.</param>
        /// <param name="axisDirection">The axis direction.</param>
        /// <param name="angle">Thhe angle to rotate, in radian.</param>
        public void Rotate(Vector3 axisPoint, Vector3 axisDirection, float angle)
        {
            axisDirection.Normalize();

            Rotate(axisDirection, angle);

            Matrix m = Matrix;
            m *= Matrix.CreateTranslation(Vector3.Negate(axisPoint));
            m *= Matrix.CreateFromAxisAngle(axisDirection, angle);
            m *= Matrix.CreateTranslation(axisPoint);

            Position = m.Translation;
        }
        /// <summary>
        /// Rotates current transformation around current forward by specified angle.
        /// </summary>
        /// <param name="angle">The angle to rotate, in radian, around current forward vector.</param>
        public void RotateAroundForward(float angle)
        {
            Rotate(Matrix.Forward, angle);
        }
        /// <summary>
        /// Rotate current transformation around current backward by specified angle.
        /// </summary>
        /// <param name="angle">The angle to rotate, in radian, around current backward vector.</param>
        public void RotateAroundBackward(float angle)
        {
            Rotate(Matrix.Backward, angle);
        }
        /// <summary>
        /// Rotates current transformation around current up by specified angle.
        /// </summary>
        /// <param name="angle">The angle to rotate, in radian, around current up vector.</param>
        public void RotateAroundUp(float angle)
        {
            Rotate(Matrix.Up, angle);
        }
        /// <summary>
        /// Rotates current transformation around current down by specified angle.
        /// </summary>
        /// <param name="angle">The angle to rotate, in radian, around current down vector.</param>
        public void RotateAroundDown(float angle)
        {
            Rotate(Matrix.Up, angle);
        }
        /// <summary>
        /// Rotates current transformation around current right by specified angle.
        /// </summary>
        /// <param name="angle">The angle to rotate, in radian, around current right vector.</param>
        public void RotateAroundRight(float angle)
        {
            Rotate(Matrix.Right, angle);
        }
        /// <summary>
        /// Rotates current transformation around current left by specified angle.
        /// </summary>
        /// <param name="angle">The angle to rotate, in radian, around current left vector.</param>
        public void RotateAroundLeft(float angle)
        {
            Rotate(Matrix.Left, angle);
        }
        /// <summary>
        /// Rotates around specified yaw, pitch, and roll.
        /// </summary>
        /// <param name="yaw">Angle of rotations, in radian, around the-y axis.</param>
        /// <param name="pitch">Angle of rotations, in radian, around the-x axis.</param>
        /// <param name="roll">Angle of rotations, in radian, around the-z axis.</param>
        public void Rotate(float yaw, float pitch, float roll)
        {
            if (yaw != 0)
                RotateAroundUp(yaw);
            if (pitch != 0)
                RotateAroundRight(pitch);
            if (roll != 0)
                RotateAroundForward(roll);
        }
        /// <summary>
        /// Rotates around specified yaw.
        /// </summary>
        /// <param name="yaw">Angle of rotations, in radian, around the-y axis.</param>
        public void RotateAroundYaw(float yaw)
        {
            RotateAroundUp(yaw);
        }
        /// <summary>
        /// Rotates around specified pitch.
        /// </summary>
        /// <param name="yaw">Angle of rotations, in radian, around the-x axis.</param>
        public void RotateAroundPitch(float pitch)
        {
            RotateAroundRight(pitch);
        }
        /// <summary>
        /// Rotates around specified roll.
        /// </summary>
        /// <param name="yaw">Angle of rotations, in radian, around the-z axis.</param>
        public void RotateAroundRoll(float roll)
        {
            RotateAroundForward(roll);
        }
        /// <summary>
        /// Rotates around from guide vector to destination using shortest rotation.
        /// </summary>
        /// <param name="guide">Guide vector relative to transformation position.</param>
        /// <param name="destination">Destination vector relative to transformation position.</param>
        public void Rotate(Vector3 guide, Vector3 destination)
        {
            Vector3 axis;
            float maxAngle;

            if (GetShortRotation(guide, destination, out axis, out maxAngle))
            {
                Rotate(axis, maxAngle);
            }
        }
        /// <summary>
        /// Gets short rotation axis and angle between two vector. Returns true if both vector is not in
        /// opposite direction therefore no axis or maxAngle retrived.
        /// </summary>
        /// <param name="source">Source vector.</param>
        /// <param name="destination">Destination vector.</param>
        /// <param name="axis">Out rotation axis.</param>
        /// <param name="maxAngle">Out rotation max angle.</param>
        public static bool GetShortRotation(Vector3 source, Vector3 destination, out Vector3 axis, out float maxAngle)
        {
            Vector3 a = Vector3.Normalize(source);
            Vector3 b = Vector3.Normalize(destination);

            if (a == Vector3.Negate(b))
            {
                axis = Vector3.Zero;
                maxAngle = 0;

                return false;
            }
            else
            {
                axis = Vector3.Normalize(Vector3.Cross(a, b));
                maxAngle = (float)Math.Acos(Vector3.Dot(a, b));

                return true;
            }
        }
        /// <summary>
        /// Translates to specified direction by specified amount.
        /// </summary>
        /// <param name="vector">The translation direction.</param>
        /// <param name="angle">The amount of translation.</param>
        public void Translate(Vector3 direction, float amount)
        {
            if (amount != 0)
            {
                direction.Normalize();     
                position = Vector3.Add(position, Vector3.Multiply(direction, amount));
            }
        }
        /// <summary>
        /// Translates forward by specified amount.
        /// </summary>
        /// <param name="amount">Amount of translation.</param>
        public void TranslateForward(float amount)
        {
            Translate(Matrix.Forward, amount);
        }
        /// <summary>
        /// Translates backward by specified amount.
        /// </summary>
        /// <param name="amount">Amount of translation.</param>
        public void TranslateBackward(float amount)
        {
            Translate(Matrix.Backward, amount);
        }
        /// <summary>
        /// Translates up by specified amount.
        /// </summary>
        /// <param name="amount">Amount of translation.</param>
        public void TranslateUp(float amount)
        {
            Translate(Matrix.Up, amount);
        }
        /// <summary>
        /// Translates down by specified amount.
        /// </summary>
        /// <param name="amount">Amount of translation.</param>
        public void TranslateDown(float amount)
        {
            Translate(Matrix.Down, amount);
        }
        /// <summary>
        /// Translates left by specified amount.
        /// </summary>
        /// <param name="amount">Amount of translation.</param>
        public void TranslateLeft(float amount)
        {
            Translate(Matrix.Left, amount);
        }
        /// <summary>
        /// Translates right by specified amount.
        /// </summary>
        /// <param name="amount">Amount of translation.</param>
        public void TranslateRight(float amount)
        {
            Translate(Matrix.Right, amount);
        }
        /// <summary>
        /// Translates by specified vector.
        /// </summary>
        /// <param name="vector">The translation vector.</param>
        public void Translate(Vector3 vector)
        {
            if (vector != Vector3.Zero)
            {
                position = Vector3.Add(position, vector);
            }
        }
        #endregion

        #region Transformations List

        protected static List<Transformation> transformationsList = new List<Transformation>();

        /// <summary>
        /// Gets transformation list.
        /// </summary>
        internal static List<Transformation> TransformationsList
        {
            get { return Transformation.transformationsList; }
        }

        /// <summary>
        /// Remove this instance from transformations list.
        /// </summary>
        /// <returns>true if succeeded; otherwise false.</returns>
        public bool Remove()
        {
            if (addedToList)
            {
                Transformation.TransformationsList.Remove(this);
                return true;
            }
            else
                return false;
        }

        #endregion
    }
}
